package org.study.ioc.core;

import org.study.ioc.bean.BeanDefinition;
import org.study.ioc.bean.ConstructorArg;
import org.study.ioc.bean.PropertyArg;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 *
 * @Author: Hukai
 * @Date: 2021/06/03/9:54
 * @Description:
 * todo 目前只支持一种方式，通过名称来注入,未来还会优化通过类型来注入
 *
 */
public class BeanFactoryImp implements BeanFactory{
    //存放已经生成的bean，key是bean的名称,value是bean的实例对象
    private static final ConcurrentHashMap<String ,Object> beanMap = new ConcurrentHashMap<String, Object>();
    //这里存放的是对bean描述的解析
    private static final ConcurrentHashMap<String, BeanDefinition>   beanDefineMap = new ConcurrentHashMap<String, BeanDefinition>();
    //通过Collections,获取一个安全的set集合
    private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<String>());
    //早期生成的对象
    private final Map<String,Object>  earlySingletonObjects = new HashMap<String, Object>(16);

    /**
     * 获取bean对象的实现
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) throws Exception {
        //通过名称找
       Object bean = beanMap.get(beanName);
       //找到了，就直接返回
       if(bean!=null){
            return bean;
        }
       Object earlyBean= earlySingletonObjects.get(beanName);
       if(earlyBean!=null){
            System.out.println("发送循环依赖,bean的名称是:"+beanName);
            return earlyBean;
       }
       //如果还没有初始化，则需要初始化bean
        BeanDefinition beanDefinition= beanDefineMap.get(beanName);
        bean = this.createBean(beanDefinition);
        if(bean!=null){
            //为了解决循环依赖，先添加到早期单例中
            earlySingletonObjects.put(beanName, bean);
            //给对象注入参数
            populatebean(bean,beanDefinition);
            //存放到容器中方便获取
            beanMap.put(beanName,bean);
            //从早期单例中移除对象
            earlySingletonObjects.remove(beanName);
            return bean;
        }
        return null;
    }

    /**
     * 给对象赋值
     * @param bean
     * @param beanDefinition
     */
    protected void  populatebean(Object bean,BeanDefinition beanDefinition) throws Exception {
        //获取对象的参数列表
        List<PropertyArg> propertyArgs = beanDefinition.getPropertyArgs();
        if (propertyArgs != null && !propertyArgs.isEmpty()) {
            for(PropertyArg propertyArg:propertyArgs){
              String pName=  propertyArg.getName();
              String pValue =propertyArg.getValue();
              String pRef=propertyArg.getRef();
              Object obj=null;
              if(pValue!=null){
                    obj=pValue;
              }else if(pRef!=null && !"".equals(pRef)){
                    obj=getBean(pRef);
              }
             Method method =   getPropertySetter(beanDefinition,pName,pValue);
              //通过调用invoke方方,来正在执行赋值的操作
              method.invoke(bean,obj);
            }
        }
    }

    /**
     * 给对象的属性赋值
     * 具体是通过set/get方式来实现
     */
   private Method  getPropertySetter(BeanDefinition beanDefinition,String pName,Object injectValue) throws ClassNotFoundException, NoSuchMethodException {
        Class clz =Class.forName(beanDefinition.getClassName());
        Class injectClazz =injectValue.getClass();
        Class supClaszz=injectValue.getClass().getSuperclass();
        //不是空，或者不是根对象
        if(supClaszz!=null && supClaszz!=Object.class){
            injectClazz=supClaszz;
        }
       pName=pName.substring(0,1).toUpperCase()+pName.substring(1);
        //组合起来是这样的，(setXxx,value)通过调用实际是通过set方式来赋值
       Method setter=clz.getMethod("set"+pName,injectClazz);
       return setter;
   }

    /**
     * 创建对象过程
     * @param beanDefinition
     * @return
     * @throws Exception
     */
    protected Object createBean(BeanDefinition beanDefinition) throws Exception {
        Object object = new Object();
        //获取到bean的全路径
        String beanClassName =  beanDefinition.getClassName();
        //获取到当前的类对象,使用  Class.forName()也是可以的，更加简单一下
        Class clz=  Thread.currentThread().getContextClassLoader().loadClass(beanClassName);
        //如果加载不到的话，存在两种情况，第一种情况是在配置bean的xml中写的有问题,spring里如果你的xml配置不对，应该也是在这里报的错
        //                          第二种情况其他未知原因
        if(clz==null){
           throw  new Exception("通过bean名称,找不到对应的配置");
        }
        //开始要实例化bean对象
        List<ConstructorArg> constructorArgs= beanDefinition.getConstructorArgs();
        if(constructorArgs!=null && constructorArgs.isEmpty()){
                ArrayList arrayList = new ArrayList();
                for (ConstructorArg arg:constructorArgs){
                    //会调用获取bean的方法,为什么要调用这个方法呢,原因如下：
                    //1.因为beanDefinition里面会配置另外一个bean的引用,可能这个bean已经加载到ioc容器里面,我们不需要在加载一遍,直接从容器里拿出来
                    //2.可能这个bean还没创建,需要我们去创建,调用getBean会自动的创建,而且bean和bean之间有三种关系
                    //    A.相互独立关系,最简单的,就是谁都不依赖谁
                    //    B.上下级依赖的关系,只有一层依赖离线 bean1中配置了一个ref指向bean2
                    //    C.相互依赖,形成一个环状,你依赖我，我依赖你
                    arrayList.add(getBean(arg.getRef()));

                }

        }
        return clz.newInstance();

    }

    /**
     * 给子类实现提供一个能够注册到beanDefineMap容器的方法
     * @param beanName
     * @param beanDefinition
     */
    public   void  regist(String beanName ,BeanDefinition beanDefinition){
             beanDefineMap.put(beanName,beanDefinition);
             beanNameSet.add(beanName);
    }
}
